﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;

namespace Allegro.TaskExecution
{
    ///<summary>
    /// Persistence context
    ///</summary>
    public class XmlPersistenceContext : IXmlPersistenceContext
    {
        /// <summary>
        /// Create an empty persistence context. This can not be used for loading XML until a namespace manager has been assigned
        /// </summary>
        public XmlPersistenceContext()
        {
            NamespaceManager = null;
        }
        ///<summary>
        ///</summary>
        ///<param name="nsMgr"></param>
        public XmlPersistenceContext(XmlNamespaceManager nsMgr)
        {
            NamespaceManager = nsMgr;
        }
        /// <summary>
        /// Xml namespace manager used for loading XML
        /// </summary>
        public XmlNamespaceManager NamespaceManager
        {
            get;
            set;
        }
    }
    ///<summary>
    /// Persistence class
    ///</summary>
    public static class XmlPersist
    {
        ///<summary>
        /// Load object from XML
        ///</summary>
        ///<param name="navigator"></param>
        ///<typeparam name="T"></typeparam>
        ///<returns></returns>
        public static T LoadObject<T>(XPathNavigator navigator)
        {
            XmlNamespaceManager nsMgr = GetNamespaceManager(navigator);
            nsMgr.AddNamespace("al", Constants.XmlNamespace);
            XmlPersistenceContext persistenceContext = new XmlPersistenceContext(nsMgr);
            return LoadObject<T>(navigator, persistenceContext);
        }
        /// <summary>
        /// Load obejct from XML
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="navigator"></param>
        /// <param name="persistenceContext"></param>
        /// <returns></returns>
        public static T LoadObject<T>(XPathNavigator navigator, IXmlPersistenceContext persistenceContext)
        {
            T instance = default(T);
            Type type = null;

            //string name = navigator.Name;
            string localName = navigator.LocalName;
            
            if (localName == "Collection")
            {
                instance = (T)LoadCollection(navigator, persistenceContext);
            }
            else
            {
                string ns = navigator.NamespaceURI;
                string clrNamespace = Constants.Namespace;
                if (!string.IsNullOrEmpty(ns))
                {
                    //localName = navigator.LocalName;
                    //string prefix = navigator.Prefix;
                    if (ns.Contains("clr-namespace:"))
                    {
                        int i = ns.IndexOf(":");
                        clrNamespace = ns.Substring(i + 1);
                    }
                }
                // Bind the type
                string typeName = string.Format("{0}.{1}", clrNamespace, localName);
                type = Type.GetType(typeName);
                if (type == null)
                {
                    AppDomain currentAppDomain = AppDomain.CurrentDomain;
                    Assembly[] assemblies = currentAppDomain.GetAssemblies();
                    foreach (Assembly assembly in assemblies)
                    {
                        type = assembly.GetType(typeName);
                        if (type != null)
                            break;
                    }
                }
                // Instantiate the type
                if (type != null)
                {
                    ConstructorInfo constructor = type.GetConstructor(new Type[0]);
                    if (constructor != null)
                    {
                        instance = (T)constructor.Invoke(new object[0]);
                        if (instance != null && instance is IXmlPersist)
                        {
                            IXmlPersist persist = instance as IXmlPersist;
                            persist.Load(navigator, persistenceContext);
                        }
                    }
                }
                else
                {
                    //Type t = typeof(T);
                    //XmlSerializer s = new XmlSerializer(t);
                    //XmlReader reader = navigator.ReadSubtree();
                    //try
                    //{
                    //    reader.MoveToContent();
                    //    object o = s.Deserialize(reader);
                    //    instance = (T)o;
                    //}
                    //catch (Exception x)
                    //{

                    //}
                    string msg = string.Format("XmlPersist.LoadObject: Unable to instantiate type {0}", typeName);
                    Log.Error(msg, null, LogEventTypes.OtherEvent);
                    //throw new TypeLoadException(msg);
                }
            }
            return instance;
        }
        ///<summary>
        ///</summary>
        ///<param name="navigator"></param>
        ///<returns></returns>
        public static object LoadObject(XPathNavigator navigator)
        {
            XmlNamespaceManager nsMgr = GetNamespaceManager(navigator);
            nsMgr.AddNamespace("al", Constants.XmlNamespace);
            XmlPersistenceContext persistenceContext = new XmlPersistenceContext(nsMgr);
            return LoadObject(navigator, persistenceContext);
        }
        ///<summary>
        ///</summary>
        ///<param name="navigator"></param>
        ///<param name="persistenceContext"></param>
        ///<returns></returns>
        public static object LoadObject(XPathNavigator navigator, IXmlPersistenceContext persistenceContext)
        {
            object instance = null;
            string nodeValue;
            string localName = navigator.LocalName;

            switch (localName)
            {
                case "Collection":
                    instance = LoadCollection(navigator, persistenceContext);
                    break;
                case "int":
                    nodeValue = navigator.Value;
                    instance = int.Parse(nodeValue);
                    break;
                case "double":
                    nodeValue = navigator.Value;
                    instance = double.Parse(nodeValue, CultureInfo.InvariantCulture);
                    break;
                case "string":
                    instance = navigator.Value;
                    break;
                default:
                    {
                        Type type = null;
                        string ns = navigator.NamespaceURI;
                        string clrNamespace = Constants.Namespace;
                        if (!string.IsNullOrEmpty(ns))
                        {
                            localName = navigator.LocalName;
                            //string prefix = navigator.Prefix;
                            if (ns.Contains("clr-namespace:"))
                            {
                                int i = ns.IndexOf(":");
                                clrNamespace = ns.Substring(i + 1);
                            }
                        }
                        // Bind the type
                        string typeName = string.Format("{0}.{1}", clrNamespace, localName);
                        type = Type.GetType(typeName);
                        if (type == null)
                        {
                            AppDomain currentAppDomain = AppDomain.CurrentDomain;
                            Assembly[] assemblies = currentAppDomain.GetAssemblies();
                            foreach (Assembly assembly in assemblies)
                            {
                                type = assembly.GetType(typeName);
                                if (type != null)
                                    break;
                            }
                        }
                        // Instantiate the type
                        if (type != null)
                        {
                            ConstructorInfo constructor = type.GetConstructor(new Type[0]);
                            if (constructor != null)
                            {
                                instance = constructor.Invoke(new object[0]);
                                if (instance != null && instance is IXmlPersist)
                                {
                                    IXmlPersist persist = instance as IXmlPersist;
                                    persist.Load(navigator, persistenceContext);
                                }
                            }
                        }
                        //else
                        //{
                        //    Type t = typeof(object);
                        //    XmlSerializer s = new XmlSerializer(t);
                        //    XmlReader reader = navigator.ReadSubtree();
                        //    try
                        //    {
                        //        reader.MoveToContent();
                        //        object o = s.Deserialize(reader);
                        //        instance = o;
                        //    }
                        //    catch (Exception x)
                        //    {
                        //    }
                        //}
                    }
                    break;
            }

            return instance;
        }
        ///<summary>
        ///</summary>
        ///<param name="navigator"></param>
        ///<param name="persistenceContext"></param>
        ///<returns></returns>
        public static IEnumerable LoadCollection(XPathNavigator navigator, IXmlPersistenceContext persistenceContext)
        {
            List<object> list = new List<object>();
            if (navigator.MoveToFirstChild())
            {
                do
                {
                    string nodeName = navigator.Name;
                    string nodeValue = navigator.Value;
                    switch (nodeName)
                    {
                        case "int":
                            int i = int.Parse(nodeValue);
                            list.Add(i);
                            break;
                        case "double":
                            double d = double.Parse(nodeValue, CultureInfo.InvariantCulture);
                            list.Add(d);
                            break;
                        case "string":
                            list.Add(nodeValue);
                            break;
                        default:
                            object value = LoadObject<object>(navigator, persistenceContext);
                            list.Add(value);
                            break;
                    }
                }
                while (navigator.MoveToNext());
            }
            return list;
        }
        ///<summary>
        ///</summary>
        ///<param name="writer"></param>
        ///<param name="o"></param>
        ///<param name="persistenceContext"></param>
        public static void SaveObject(XmlWriter writer, object o, IXmlPersistenceContext persistenceContext)
        {
            if (o is IXmlPersist)
            {
                IXmlPersist p = o as IXmlPersist;
                p.Save(writer, persistenceContext);
            }
            else if (o is int)
            {
                int i = (int)o;
                writer.WriteElementString("int", Constants.XmlNamespace, i.ToString());
            }
            else if (o is double)
            {
                double d = (double)o;
                writer.WriteElementString("double", Constants.XmlNamespace,
                                          d.ToString(CultureInfo.InvariantCulture));
            }
            else if (o is string)
            {
                string s = (string)o;
                writer.WriteElementString("string", Constants.XmlNamespace, s);
            }
            else if (o is IEnumerable)
            {
                SaveCollection(writer, o, persistenceContext);
            }
            
            //else
            //{
            //    // Do the best we can
            //    Type t = o.GetType();
            //    XmlSerializer s = new XmlSerializer(t, Constants.XmlNamespace);
            //    XmlSerializerNamespaces serializerNamespaces = new XmlSerializerNamespaces();
            //    //IDictionary<string, string> namespaces = persistenceContext.NamespaceManager.GetNamespacesInScope(XmlNamespaceScope.All);
            //    //foreach(string ns in namespaces.Keys)
            //    //{
            //    //    serializerNamespaces.Add(ns, namespaces[ns]);
            //    //}
            //    serializerNamespaces.Add(Constants.XmlDefaultNamespacePrefix, Constants.XmlNamespace);
            //    s.Serialize(writer, o, serializerNamespaces);
            //}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="o"></param>
        /// <param name="persistenceContext"></param>
        public static void SaveCollection(XmlWriter writer, object o, IXmlPersistenceContext persistenceContext)
        {
            writer.WriteStartElement("Collection", Constants.XmlNamespace);
            IEnumerable collection = o as IEnumerable;
            foreach (object item in collection)
            {
                if (item is int)
                {
                    int i = (int)item;
                    writer.WriteElementString("int", Constants.XmlNamespace, i.ToString());
                }
                else if (item is double)
                {
                    double d = (double)item;
                    writer.WriteElementString("double", Constants.XmlNamespace,
                                              d.ToString(CultureInfo.InvariantCulture));
                }
                else if (item is string)
                {
                    string s = (string)item;
                    writer.WriteElementString("string", Constants.XmlNamespace, s);
                }
                else
                {
                    writer.WriteStartElement("Value", Constants.XmlNamespace);
                    SaveObject(writer, item, persistenceContext);
                    writer.WriteEndElement();
                }
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// Get a namespace resolver
        /// </summary>
        /// <param name="navigator"></param>
        /// <returns></returns>
        public static XmlNamespaceManager GetNamespaceManager(XPathNavigator navigator)
        {
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(navigator.NameTable);
            IDictionary<string, string> namespaces = navigator.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
            foreach (string ns in namespaces.Keys)
            {
                nsMgr.AddNamespace(ns, namespaces[ns]);
            }
            return nsMgr;
        }

    }
    //public class XmlLoader
    //{
    //    /// <summary>
    //    /// The XmlLoader creates an instance of the class indicated by the classRef attribute on the root element of the navigator.
    //    /// The default constructor of the class is used. An exception is thrown if there is no default constructor.
    //    /// If there is no such attribute, null is returned. Furthermore, the class must implement the IXmlPersist interface. After
    //    /// instantiation of the object, the IXmlPersist.Load method is called.
    //    /// </summary>
    //    /// <param name="navigator"></param>
    //    /// <returns></returns>
    //    public static object LoadClass(XPathNavigator navigator)
    //    {
    //        object instance = null;
    //        Type type = null;
    //        XPathNavigator attribute = navigator.SelectSingleNode("@classRef");
    //        if (attribute != null)
    //        {
    //            string classRef = attribute.Value;
    //            if (!string.IsNullOrEmpty(classRef))
    //            {
    //                type = Type.GetType(classRef);
    //            }
    //        }
    //        else
    //        {
    //            string ns = navigator.NamespaceURI;
    //            if (!string.IsNullOrEmpty(ns))
    //            {
    //                string localName = navigator.LocalName;
    //                string prefix = navigator.Prefix;
    //                if (ns.Contains("clr-namespace:"))
    //                {
    //                    int i = ns.IndexOf(":");
    //                    string clrNamespace = ns.Substring(i + 1);
    //                    string typeName = string.Format("{0}.{1}", clrNamespace, localName);
    //                    type = Type.GetType(typeName);
    //                    if (type == null)
    //                    {
    //                        AppDomain currentAppDomain = AppDomain.CurrentDomain;
    //                        Assembly[] assemblies = currentAppDomain.GetAssemblies();
    //                        foreach (Assembly assembly in assemblies)
    //                        {
    //                            type = assembly.GetType(typeName);
    //                            if (type != null)
    //                                break;
    //                        }
    //                    }
    //                }
    //            }
    //        }
    //        if (type != null)
    //        {
    //            ConstructorInfo construcor = type.GetConstructor(new Type[0]);
    //            if (construcor != null)
    //            {
    //                instance = construcor.Invoke(new object[0]);
    //                if (instance != null && instance is IXmlPersist)
    //                {
    //                    IXmlPersist persist = instance as IXmlPersist;
    //                    persist.Load(navigator);
    //                }
    //            }
    //        }
    //        return instance;
    //    }

    //    public static XmlNamespaceManager GetNamespaceManager(XPathNavigator navigator)
    //    {
    //        XmlNamespaceManager nsMgr = new XmlNamespaceManager(navigator.NameTable);
    //        IDictionary<string, string> namespaces = navigator.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
    //        foreach (string ns in namespaces.Keys)
    //        {
    //            nsMgr.AddNamespace(ns, namespaces[ns]);
    //        }
    //        return nsMgr;
    //    }
    //    public static string GetQuery(XPathNavigator navigator, string rawQuery)
    //    {
    //        string nsPrefix = navigator.Prefix;
    //        string query = (string.IsNullOrEmpty(nsPrefix))
    //                           ? rawQuery
    //                           : string.Format("{0}:{1}", nsPrefix, rawQuery);
    //        return query;
    //    }
    //}
}
